import { expect } from '@playwright/test'
import {
  expectPlausibleInAction,
  hideAndShowCurrentTab,
  focus,
  blur,
  blurAndFocusPage,
  tracker_script_version
} from './support/test-utils'
import { test } from '@playwright/test'
import { LOCAL_SERVER_ADDR } from './support/server'

test.describe('engagement events', () => {
  test('sends an engagement event with time measurement when navigating to the next page', async ({
    page
  }) => {
    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement.html'),
      expectedRequests: [{ n: 'pageview' }]
    })

    await page.waitForTimeout(1000)

    const [request] = await expectPlausibleInAction(page, {
      action: () => page.click('#navigate-away'),
      expectedRequests: [
        {
          n: 'engagement',
          u: `${LOCAL_SERVER_ADDR}/engagement.html`,
          v: tracker_script_version
        }
      ]
    })

    expect(request.e).toBeGreaterThan(1000)
    expect(request.e).toBeLessThan(1500)
  })

  test('sends an event and a pageview on hash-based SPA navigation', async ({
    page
  }) => {
    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement-hash.html'),
      expectedRequests: [{ n: 'pageview' }]
    })

    await page.waitForTimeout(1000)

    const [request] = await expectPlausibleInAction(page, {
      action: () => page.click('#hash-nav'),
      expectedRequests: [
        { n: 'engagement', u: `${LOCAL_SERVER_ADDR}/engagement-hash.html` },
        {
          n: 'pageview',
          u: `${LOCAL_SERVER_ADDR}/engagement-hash.html#some-hash`
        }
      ]
    })

    expect(request.e).toBeGreaterThan(1000)
    expect(request.e).toBeLessThan(1500)
  })

  test('sends an event and a pageview on history-based SPA navigation', async ({
    page
  }) => {
    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement.html'),
      expectedRequests: [{ n: 'pageview' }]
    })

    await page.waitForTimeout(1000)

    const [request] = await expectPlausibleInAction(page, {
      action: () => page.click('#history-nav'),
      expectedRequests: [
        { n: 'engagement', u: `${LOCAL_SERVER_ADDR}/engagement.html` },
        { n: 'pageview', u: `${LOCAL_SERVER_ADDR}/another-page` }
      ]
    })

    expect(request.e).toBeGreaterThan(1000)
    expect(request.e).toBeLessThan(1500)
  })

  test('sends engagements when pageviews are triggered manually on a SPA', async ({
    page
  }) => {
    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement-hash-manual.html'),
      expectedRequests: [{ n: 'pageview' }]
    })

    await page.waitForTimeout(1000)

    const [request] = await expectPlausibleInAction(page, {
      action: () => page.click('#about-us-hash-link'),
      expectedRequests: [
        { n: 'engagement', u: `${LOCAL_SERVER_ADDR}/#home` },
        { n: 'pageview', u: `${LOCAL_SERVER_ADDR}/#about-us` }
      ]
    })

    expect(request.e).toBeGreaterThan(1000)
    expect(request.e).toBeLessThan(1500)
  })

  test('sends an event with the manually overridden URL', async ({ page }) => {
    await page.goto('/engagement-manual.html')

    await expectPlausibleInAction(page, {
      action: () => page.click('#pageview-trigger-custom-url'),
      expectedRequests: [
        { n: 'pageview', u: 'https://example.com/custom/location' }
      ]
    })

    await expectPlausibleInAction(page, {
      action: () => page.click('#navigate-away'),
      expectedRequests: [
        { n: 'engagement', u: 'https://example.com/custom/location' }
      ]
    })
  })

  test('does not send an event when pageview was not sent in manual mode', async ({
    page
  }) => {
    await page.goto('/engagement-manual.html')

    await expectPlausibleInAction(page, {
      action: () => page.click('#navigate-away'),
      refutedRequests: [{ n: 'engagement' }]
    })
  })

  test('script.exclusions.hash.js sends an event only from URLs where a pageview was sent', async ({
    page
  }) => {
    const pageBaseURL = `${LOCAL_SERVER_ADDR}/engagement-hash-exclusions.html`

    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement-hash-exclusions.html'),
      expectedRequests: [{ n: 'pageview' }]
    })

    // After the initial pageview is sent, navigate to ignored page ->
    // engagement event is sent from the initial page URL
    await expectPlausibleInAction(page, {
      action: () => page.click('#ignored-hash-link'),
      expectedRequests: [{ n: 'engagement', u: pageBaseURL, h: 1 }]
    })

    // Navigate from ignored page to a tracked page ->
    // no engagement from the current page, pageview on the next page
    await expectPlausibleInAction(page, {
      action: () => page.click('#hash-link-1'),
      expectedRequests: [{ n: 'pageview', u: `${pageBaseURL}#hash1`, h: 1 }],
      refutedRequests: [{ n: 'engagement' }]
    })

    // Navigate from a tracked page to another tracked page ->
    // engagement with the last page URL, pageview with the new URL
    await expectPlausibleInAction(page, {
      action: () => page.click('#hash-link-2'),
      expectedRequests: [
        { n: 'engagement', u: `${pageBaseURL}#hash1`, h: 1 },
        { n: 'pageview', u: `${pageBaseURL}#hash2`, h: 1 }
      ]
    })
  })

  test('sends an event with the same props as pageview (manual extension)', async ({
    page
  }) => {
    await page.goto('/engagement-manual.html')

    await expectPlausibleInAction(page, {
      action: () => page.click('#pageview-trigger-custom-props'),
      expectedRequests: [{ n: 'pageview', p: { author: 'John' } }]
    })

    await expectPlausibleInAction(page, {
      action: () => page.click('#navigate-away'),
      expectedRequests: [{ n: 'engagement', p: { author: 'John' } }]
    })
  })

  test('sends an event with the same props as pageview (pageview-props extension)', async ({
    page
  }) => {
    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement-pageview-props.html'),
      expectedRequests: [{ n: 'pageview', p: { author: 'John', index: '0' } }]
    })

    await expectPlausibleInAction(page, {
      action: () => page.click('#navigate-away'),
      expectedRequests: [{ n: 'engagement', p: { author: 'John', index: '0' } }]
    })
  })

  test('pageview props with custom events and values changing mid-view (pageview-props extension)', async ({
    page
  }) => {
    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement-pageview-props.html'),
      expectedRequests: [{ n: 'pageview', p: { author: 'John', index: '0' } }]
    })

    await page.click('#increment-event-index')

    await expectPlausibleInAction(page, {
      action: () => page.click('#custom-event-button'),
      expectedRequests: [
        { n: 'Custom event', p: { author: 'Karl', index: '1' } }
      ]
    })

    await expectPlausibleInAction(page, {
      action: () => page.click('#navigate-away'),
      expectedRequests: [{ n: 'engagement', p: { author: 'John', index: '0' } }]
    })
  })

  test('sends an event with the same props as pageview (hash navigation / pageview-props extension)', async ({
    page
  }) => {
    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement-hash-pageview-props.html'),
      expectedRequests: [{ n: 'pageview', p: {} }]
    })

    await expectPlausibleInAction(page, {
      action: () => page.click('#john-post'),
      expectedRequests: [
        { n: 'engagement', p: {} },
        { n: 'pageview', p: { author: 'john' } }
      ]
    })

    await expectPlausibleInAction(page, {
      action: () => page.click('#jane-post'),
      expectedRequests: [
        { n: 'engagement', p: { author: 'john' } },
        { n: 'pageview', p: { author: 'jane' } }
      ]
    })

    await expectPlausibleInAction(page, {
      action: () => page.click('#home'),
      expectedRequests: [
        { n: 'engagement', p: { author: 'jane' } },
        { n: 'pageview', p: {} }
      ]
    })
  })

  test('sends an event when plausible API is slow and user navigates away before response is received', async ({
    page,
    browserName
  }) => {
    test.skip(browserName === 'chromium', 'flaky')

    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement.html'),
      expectedRequests: [
        { n: 'pageview', u: `${LOCAL_SERVER_ADDR}/engagement.html` }
      ]
    })

    await expectPlausibleInAction(page, {
      action: async () => {
        await page.click('#to-pageleave-pageview-props')
        await page.waitForTimeout(500)
        await page.click('#back-button-trigger')
      },
      expectedRequests: [
        { n: 'engagement', u: `${LOCAL_SERVER_ADDR}/engagement.html` },
        {
          n: 'pageview',
          u: `${LOCAL_SERVER_ADDR}/engagement-pageview-props.html`,
          p: { author: 'John', index: '0' }
        },
        {
          n: 'engagement',
          u: `${LOCAL_SERVER_ADDR}/engagement-pageview-props.html`,
          p: { author: 'John', index: '0' }
        },
        { n: 'pageview', u: `${LOCAL_SERVER_ADDR}/engagement.html` }
      ],
      responseDelay: 1000
    })
  })

  test('sends engagement events when tab toggles between foreground and background', async ({
    page
  }) => {
    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement.html'),
      expectedRequests: [{ n: 'pageview' }]
    })

    const [request1] = await expectPlausibleInAction(page, {
      action: () => hideAndShowCurrentTab(page, { delay: 2000 }),
      expectedRequests: [
        { n: 'engagement', u: `${LOCAL_SERVER_ADDR}/engagement.html` }
      ]
    })
    expect(request1.e).toBeLessThan(500)

    await page.waitForTimeout(3000)

    const [request2] = await expectPlausibleInAction(page, {
      action: () => hideAndShowCurrentTab(page, { delay: 2000 }),
      expectedRequests: [
        { n: 'engagement', u: `${LOCAL_SERVER_ADDR}/engagement.html` }
      ]
    })

    expect(request2.e).toBeGreaterThan(3000)
    expect(request2.e).toBeLessThan(3500)
  })

  test('does not send engagement events when tab is only open for a short time until over 3000ms has passed', async ({
    page
  }) => {
    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement.html'),
      expectedRequests: [{ n: 'pageview' }]
    })

    const [request1] = await expectPlausibleInAction(page, {
      action: () => hideAndShowCurrentTab(page),
      expectedRequests: [
        { n: 'engagement', u: `${LOCAL_SERVER_ADDR}/engagement.html` }
      ]
    })
    expect(request1.e).toBeLessThan(500)

    await page.waitForTimeout(500)

    await expectPlausibleInAction(page, {
      action: () => hideAndShowCurrentTab(page),
      refutedRequests: [{ n: 'engagement' }],
      mockRequestTimeout: 100
    })

    await page.waitForTimeout(2500)

    const [request2] = await expectPlausibleInAction(page, {
      action: () => hideAndShowCurrentTab(page, { delay: 3000 }),
      expectedRequests: [
        { n: 'engagement', u: `${LOCAL_SERVER_ADDR}/engagement.html` }
      ]
    })

    // Sum of both visibility times
    expect(request2.e).toBeGreaterThan(3000)
    expect(request2.e).toBeLessThan(3500)
  })

  test('tracks engagement time properly in a SPA', async ({ page }) => {
    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement-hash.html'),
      expectedRequests: [{ n: 'pageview' }]
    })

    await page.waitForTimeout(1000)

    const [request] = await expectPlausibleInAction(page, {
      action: () => page.click('#hash-nav'),
      expectedRequests: [
        { n: 'engagement', u: `${LOCAL_SERVER_ADDR}/engagement-hash.html` },
        {
          n: 'pageview',
          u: `${LOCAL_SERVER_ADDR}/engagement-hash.html#some-hash`
        }
      ]
    })

    expect(request.e).toBeGreaterThan(1000)
    expect(request.e).toBeLessThan(1500)

    const [request2] = await expectPlausibleInAction(page, {
      action: () => page.click('#hash-nav-2'),
      expectedRequests: [
        {
          n: 'engagement',
          u: `${LOCAL_SERVER_ADDR}/engagement-hash.html#some-hash`
        },
        {
          n: 'pageview',
          u: `${LOCAL_SERVER_ADDR}/engagement-hash.html#another-hash`
        }
      ]
    })

    expect(request2.e).toBeLessThan(200)

    await page.waitForTimeout(3000)

    const [request3] = await expectPlausibleInAction(page, {
      action: () => hideAndShowCurrentTab(page),
      expectedRequests: [
        {
          n: 'engagement',
          u: `${LOCAL_SERVER_ADDR}/engagement-hash.html#another-hash`
        }
      ]
    })

    expect(request3.e).toBeGreaterThan(3000)
    expect(request3.e).toBeLessThan(3500)

    await page.waitForTimeout(3000)
    const [request4] = await expectPlausibleInAction(page, {
      action: () => page.click('#hash-nav'),
      expectedRequests: [
        {
          n: 'engagement',
          u: `${LOCAL_SERVER_ADDR}/engagement-hash.html#another-hash`
        },
        {
          n: 'pageview',
          u: `${LOCAL_SERVER_ADDR}/engagement-hash.html#some-hash`
        }
      ]
    })

    expect(request4.e).toBeGreaterThan(3000)
    expect(request4.e).toBeLessThan(3500)
  })

  test('tracks engagement time whilst tab gains and loses focus', async ({
    page
  }) => {
    await expectPlausibleInAction(page, {
      action: () => page.goto('/engagement.html'),
      expectedRequests: [{ n: 'pageview' }]
    })

    const [request1] = await expectPlausibleInAction(page, {
      action: () => blur(page),
      expectedRequests: [
        { n: 'engagement', u: `${LOCAL_SERVER_ADDR}/engagement.html` }
      ]
    })
    expect(request1.e).toBeLessThan(500)

    await focus(page)
    await page.waitForTimeout(1000)

    await expectPlausibleInAction(page, {
      action: () => blurAndFocusPage(page, { delay: 3000 }),
      refutedRequests: [{ n: 'engagement' }],
      mockRequestTimeout: 100
    })

    await page.waitForTimeout(2500)

    const [request2] = await expectPlausibleInAction(page, {
      action: () => blur(page),
      expectedRequests: [
        { n: 'engagement', u: `${LOCAL_SERVER_ADDR}/engagement.html` }
      ]
    })

    expect(request2.e).toBeGreaterThan(3500)
    expect(request2.e).toBeLessThan(4000)
  })
})
